// ----------------------------------------------------------------------------
// Buzz, a Javascript HTML5 Audio library
// v 1.0.4 beta
// Licensed under the MIT license.
// http://buzz.jaysalvat.com/
// ----------------------------------------------------------------------------
// Copyright (C) 2011 Jay Salvat
// http://jaysalvat.com/
// ----------------------------------------------------------------------------
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files ( the "Software" ), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ----------------------------------------------------------------------------

var buzz = {
  defaults: {
    autoplay: false,
    duration: 5000,
    formats: [],
    loop: false,
    placeholder: '--',
    preload: 'metadata',
    volume: 80
  },
  types: {
    'mp3': 'audio/mpeg',
    'ogg': 'audio/ogg',
    'wav': 'audio/wav',
    'aac': 'audio/aac',
    'm4a': 'audio/x-m4a'
  },
  sounds: [],
  el: document.createElement('audio'),

  sound: function (src, options) {
    options = options || {};

    var pid = 0, events = [], eventsOnce = {}, supported = buzz.isSupported();

    // publics
    this.load = function () {
      if (!supported) {
        return this;
      }

      this.sound.load();
      return this;
    };

    this.play = function () {
      if (!supported) {
        return this;
      }

      this.sound.play();
      return this;
    };

    this.togglePlay = function () {
      if (!supported) {
        return this;
      }

      if (this.sound.paused) {
        this.sound.play();
      } else {
        this.sound.pause();
      }
      return this;
    };

    this.pause = function () {
      if (!supported) {
        return this;
      }

      this.sound.pause();
      return this;
    };

    this.isPaused = function () {
      if (!supported) {
        return null;
      }

      return this.sound.paused;
    };

    this.stop = function () {
      if (!supported) {
        return this;
      }

      this.setTime(this.getDuration());
      this.sound.pause();
      return this;
    };

    this.isEnded = function () {
      if (!supported) {
        return null;
      }

      return this.sound.ended;
    };

    this.loop = function () {
      if (!supported) {
        return this;
      }

      this.sound.loop = 'loop';
      this.bind('ended.buzzloop', function () {
        this.currentTime = 0;
        this.play();
      });
      return this;
    };

    this.unloop = function () {
      if (!supported) {
        return this;
      }

      this.sound.removeAttribute('loop');
      this.unbind('ended.buzzloop');
      return this;
    };

    this.mute = function () {
      if (!supported) {
        return this;
      }

      this.sound.muted = true;
      return this;
    };

    this.unmute = function () {
      if (!supported) {
        return this;
      }

      this.sound.muted = false;
      return this;
    };

    this.toggleMute = function () {
      if (!supported) {
        return this;
      }

      this.sound.muted = !this.sound.muted;
      return this;
    };

    this.isMuted = function () {
      if (!supported) {
        return null;
      }

      return this.sound.muted;
    };

    this.setVolume = function (volume) {
      if (!supported) {
        return this;
      }

      if (volume < 0) {
        volume = 0;
      }
      if (volume > 100) {
        volume = 100;
      }

      this.volume = volume;
      this.sound.volume = volume / 100;
      return this;
    };

    this.getVolume = function () {
      if (!supported) {
        return this;
      }

      return this.volume;
    };

    this.increaseVolume = function (value) {
      return this.setVolume(this.volume + (value || 1));
    };

    this.decreaseVolume = function (value) {
      return this.setVolume(this.volume - (value || 1));
    };

    this.setTime = function (time) {
      if (!supported) {
        return this;
      }

      this.whenReady(function () {
        this.sound.currentTime = time;
      });
      return this;
    };

    this.getTime = function () {
      if (!supported) {
        return null;
      }

      var time = Math.round(this.sound.currentTime * 100) / 100;
      return isNaN(time) ? buzz.defaults.placeholder : time;
    };

    this.setPercent = function (percent) {
      if (!supported) {
        return this;
      }

      return this.setTime(buzz.fromPercent(percent, this.sound.duration));
    };

    this.getPercent = function () {
      if (!supported) {
        return null;
      }

      var percent = Math.round(buzz.toPercent(this.sound.currentTime, this.sound.duration));
      return isNaN(percent) ? buzz.defaults.placeholder : percent;
    };

    this.setSpeed = function (duration) {
      if (!supported) {
        return this;
      }

      this.sound.playbackRate = duration;
    };

    this.getSpeed = function () {
      if (!supported) {
        return null;
      }

      return this.sound.playbackRate;
    };

    this.getDuration = function () {
      if (!supported) {
        return null;
      }

      var duration = Math.round(this.sound.duration * 100) / 100;
      return isNaN(duration) ? buzz.defaults.placeholder : duration;
    };

    this.getPlayed = function () {
      if (!supported) {
        return null;
      }

      return timerangeToArray(this.sound.played);
    };

    this.getBuffered = function () {
      if (!supported) {
        return null;
      }

      return timerangeToArray(this.sound.buffered);
    };

    this.getSeekable = function () {
      if (!supported) {
        return null;
      }

      return timerangeToArray(this.sound.seekable);
    };

    this.getErrorCode = function () {
      if (supported && this.sound.error) {
        return this.sound.error.code;
      }
      return 0;
    };

    this.getErrorMessage = function () {
      if (!supported) {
        return null;
      }

      switch (this.getErrorCode()) {
        case 1:
          return 'MEDIA_ERR_ABORTED';
        case 2:
          return 'MEDIA_ERR_NETWORK';
        case 3:
          return 'MEDIA_ERR_DECODE';
        case 4:
          return 'MEDIA_ERR_SRC_NOT_SUPPORTED';
        default:
          return null;
      }
    };

    this.getStateCode = function () {
      if (!supported) {
        return null;
      }

      return this.sound.readyState;
    };

    this.getStateMessage = function () {
      if (!supported) {
        return null;
      }

      switch (this.getStateCode()) {
        case 0:
          return 'HAVE_NOTHING';
        case 1:
          return 'HAVE_METADATA';
        case 2:
          return 'HAVE_CURRENT_DATA';
        case 3:
          return 'HAVE_FUTURE_DATA';
        case 4:
          return 'HAVE_ENOUGH_DATA';
        default:
          return null;
      }
    };

    this.getNetworkStateCode = function () {
      if (!supported) {
        return null;
      }

      return this.sound.networkState;
    };

    this.getNetworkStateMessage = function () {
      if (!supported) {
        return null;
      }

      switch (this.getNetworkStateCode()) {
        case 0:
          return 'NETWORK_EMPTY';
        case 1:
          return 'NETWORK_IDLE';
        case 2:
          return 'NETWORK_LOADING';
        case 3:
          return 'NETWORK_NO_SOURCE';
        default:
          return null;
      }
    };

    this.set = function (key, value) {
      if (!supported) {
        return this;
      }

      this.sound[key] = value;
      return this;
    };

    this.get = function (key) {
      if (!supported) {
        return null;
      }

      return key ? this.sound[key] : this.sound;
    };

    this.bind = function (types, func) {
      if (!supported) {
        return this;
      }
      types = types.split(' ');

      var that = this, efunc = function (e) {
        func.call(that, e);
      };
      for (var t = 0; t < types.length; t++) {
        var type = types[t], idx = type;
        type = idx.split('.')[0];

        events.push({
          idx: idx,
          func: efunc
        });
        this.sound.addEventListener(type, efunc, true);
      }
      return this;
    };

    this.unbind = function (types) {
      if (!supported) {
        return this;
      }
      types = types.split(' ');

      for (var t = 0; t < types.length; t++) {
        var idx = types[t], type = idx.split('.')[0];

        for (var i = 0; i < events.length; i++) {
          var namespace = events[i].idx.split('.');
          if (events[i].idx == idx || (namespace[1] && namespace[1] == idx.replace('.', ''))) {
            this.sound.removeEventListener(type, events[i].func, true);
            delete events[i];
          }
        }
      }
      return this;
    };

    this.bindOnce = function (type, func) {
      if (!supported) {
        return this;
      }

      var that = this;

      eventsOnce[pid++] = false;
      this.bind(pid + type, function () {
        if (!eventsOnce[pid]) {
          eventsOnce[pid] = true;
          func.call(that);
        }
        that.unbind(pid + type);
      });
    };

    this.trigger = function (types) {
      if (!supported) {
        return this;
      }
      types = types.split(' ');

      for (var t = 0; t < types.length; t++) {
        var idx = types[t];

        for (var i = 0; i < events.length; i++) {
          var eventType = events[i].idx.split('.');
          if (events[i].idx == idx || (eventType[0] && eventType[0] == idx.replace('.', ''))) {
            var evt = document.createEvent('HTMLEvents');
            evt.initEvent(eventType[0], false, true);
            this.sound.dispatchEvent(evt);
          }
        }
      }
      return this;
    };

    this.fadeTo = function (to, duration, callback) {
      if (!supported) {
        return this;
      }

      if (duration instanceof Function) {
        callback = duration;
        duration = buzz.defaults.duration;
      } else {
        duration = duration || buzz.defaults.duration;
      }

      var from = this.volume, delay = duration / Math.abs(from - to), that = this;
      this.play();

      function doFade() {
        setTimeout(function () {
          if (from < to && that.volume < to) {
            that.setVolume(that.volume += 1);
            doFade();
          } else if (from > to && that.volume > to) {
            that.setVolume(that.volume -= 1);
            doFade();
          } else if (callback instanceof Function) {
            callback.apply(that);
          }
        }, delay);
      }


      this.whenReady(function () {
        doFade();
      });
      return this;
    };

    this.fadeIn = function (duration, callback) {
      if (!supported) {
        return this;
      }

      return this.setVolume(0).fadeTo(100, duration, callback);
    };

    this.fadeOut = function (duration, callback) {
      if (!supported) {
        return this;
      }

      return this.fadeTo(0, duration, callback);
    };

    this.fadeWith = function (sound, duration) {
      if (!supported) {
        return this;
      }

      this.fadeOut(duration, function () {
        this.stop();
      });

      sound.play().fadeIn(duration);

      return this;
    };

    this.whenReady = function (func) {
      if (!supported) {
        return null;
      }

      var that = this;
      if (this.sound.readyState === 0) {
        this.bind('canplay.buzzwhenready', function () {
          func.call(that);
        });
      } else {
        func.call(that);
      }
    };
    // privates
    function timerangeToArray(timeRange) {
      var array = [], length = timeRange.length - 1;

      for (var i = 0; i <= length; i++) {
        array.push({
          start: timeRange.start(length),
          end: timeRange.end(length)
        });
      }
      return array;
    }

    function getExt(filename) {
      return filename.split('.').pop();
    }

    function addSource(sound, src) {
      var source = document.createElement('source');
      source.src = src;
      if (buzz.types[ getExt(src)]) {
        source.type = buzz.types[ getExt(src)];
      }
      sound.appendChild(source);
    }

    // init
    if (supported) {

      for (var i in buzz.defaults) {
        if (buzz.defaults.hasOwnProperty(i)) {
          options[i] = options[i] || buzz.defaults[i];
        }
      }

      this.sound = document.createElement('audio');

      if (src instanceof Array) {
        for (var j in src) {
          if (src.hasOwnProperty(j)) {
            addSource(this.sound, src[j]);
          }
        }
      } else if (options.formats.length) {
        for (var k in options.formats) {
          if (options.formats.hasOwnProperty(k)) {
            addSource(this.sound, src + '.' + options.formats[k]);
          }
        }
      } else {
        addSource(this.sound, src);
      }

      if (options.loop) {
        this.loop();
      }

      if (options.autoplay) {
        this.sound.autoplay = 'autoplay';
      }

      if (options.preload === true) {
        this.sound.preload = 'auto';
      } else if (options.preload === false) {
        this.sound.preload = 'none';
      } else {
        this.sound.preload = options.preload;
      }

      this.setVolume(options.volume);

      buzz.sounds.push(this);
    }
  },
  group: function (sounds) {
    sounds = argsToArray(sounds, arguments);

    // publics
    this.getSounds = function () {
      return sounds;
    };

    this.add = function (soundArray) {
      soundArray = argsToArray(soundArray, arguments);
      for (var a = 0; a < soundArray.length; a++) {
        sounds.push(soundArray[a]);
      }
    };

    this.remove = function (soundArray) {
      soundArray = argsToArray(soundArray, arguments);
      for (var a = 0; a < soundArray.length; a++) {
        for (var i = 0; i < sounds.length; i++) {
          if (sounds[i] == soundArray[a]) {
            delete sounds[i];
            break;
          }
        }
      }
    };

    this.load = function () {
      fn('load');
      return this;
    };

    this.play = function () {
      fn('play');
      return this;
    };

    this.togglePlay = function () {
      fn('togglePlay');
      return this;
    };

    this.pause = function (time) {
      fn('pause', time);
      return this;
    };

    this.stop = function () {
      fn('stop');
      return this;
    };

    this.mute = function () {
      fn('mute');
      return this;
    };

    this.unmute = function () {
      fn('unmute');
      return this;
    };

    this.toggleMute = function () {
      fn('toggleMute');
      return this;
    };

    this.setVolume = function (volume) {
      fn('setVolume', volume);
      return this;
    };

    this.increaseVolume = function (value) {
      fn('increaseVolume', value);
      return this;
    };

    this.decreaseVolume = function (value) {
      fn('decreaseVolume', value);
      return this;
    };

    this.loop = function () {
      fn('loop');
      return this;
    };

    this.unloop = function () {
      fn('unloop');
      return this;
    };

    this.setTime = function (time) {
      fn('setTime', time);
      return this;
    };

    this.setduration = function (duration) {
      fn('setduration', duration);
      return this;
    };

    this.set = function (key, value) {
      fn('set', key, value);
      return this;
    };

    this.bind = function (type, func) {
      fn('bind', type, func);
      return this;
    };

    this.unbind = function (type) {
      fn('unbind', type);
      return this;
    };

    this.bindOnce = function (type, func) {
      fn('bindOnce', type, func);
      return this;
    };

    this.trigger = function (type) {
      fn('trigger', type);
      return this;
    };

    this.fade = function (from, to, duration, callback) {
      fn('fade', from, to, duration, callback);
      return this;
    };

    this.fadeIn = function (duration, callback) {
      fn('fadeIn', duration, callback);
      return this;
    };

    this.fadeOut = function (duration, callback) {
      fn('fadeOut', duration, callback);
      return this;
    };
    // privates
    function fn() {
      var args = argsToArray(null, arguments), func = args.shift();

      for (var i = 0; i < sounds.length; i++) {
        sounds[ i ][func].apply(sounds[i], args);
      }
    }

    function argsToArray(array, args) {
      return (array instanceof Array) ? array : Array.prototype.slice.call(args);
    }

  },
  all: function () {
    return new buzz.group(buzz.sounds);
  },
  isSupported: function () {
    return !!buzz.el.canPlayType;
  },
  isOGGSupported: function () {
    return !!buzz.el.canPlayType && buzz.el.canPlayType('audio/ogg; codecs="vorbis"');
  },
  isWAVSupported: function () {
    return !!buzz.el.canPlayType && buzz.el.canPlayType('audio/wav; codecs="1"');
  },
  isMP3Supported: function () {
    return !!buzz.el.canPlayType && buzz.el.canPlayType('audio/mpeg;');
  },
  isAACSupported: function () {
    return !!buzz.el.canPlayType && (buzz.el.canPlayType('audio/x-m4a;') || buzz.el.canPlayType('audio/aac;'));
  },
  toTimer: function (time, withHours) {
    var h, m, s;
    h = Math.floor(time / 3600);
    h = isNaN(h) ? '--' : (h >= 10) ? h : '0' + h;
    m = withHours ? Math.floor(time / 60 % 60) : Math.floor(time / 60);
    m = isNaN(m) ? '--' : (m >= 10) ? m : '0' + m;
    s = Math.floor(time % 60);
    s = isNaN(s) ? '--' : (s >= 10) ? s : '0' + s;
    return withHours ? h + ':' + m + ':' + s : m + ':' + s;
  },
  fromTimer: function (time) {
    var splits = time.toString().split(':');
    if (splits && splits.length == 3) {
      time = (parseInt(splits[0], 10) * 3600) + (parseInt(splits[1], 10) * 60) + parseInt(splits[2], 10);
    }
    if (splits && splits.length == 2) {
      time = (parseInt(splits[0], 10) * 60) + parseInt(splits[1], 10);
    }
    return time;
  },
  toPercent: function (value, total, decimal) {
    var r = Math.pow(10, decimal || 0);

    return Math.round(((value * 100) / total) * r) / r;
  },
  fromPercent: function (percent, total, decimal) {
    var r = Math.pow(10, decimal || 0);

    return Math.round(((total / 100) * percent) * r) / r;
  }
};
